Skip to content

从 mmap 理解进程地址空间

  • 通义千问=》Manus
  • 大语言模型是不固定的,它会出现幻觉

进程的地址空间

进程的初始状态(execve 后复位的状态)

- 寄存器:可直接打印
- 内存:字节数组。==内存的绝大部分空间都不可访问==
	- 写一段c代码,把main赋值给一个指针,然后解引用,然后输出。会打印出main在地址空间的地址,但是不能对该指针赋值。
	- 内存只能ld、sd

Registers

RegisterHex ValueDecimal Value
rax0x00000000000000000
rbx0x00000000000000000
rcx0x00000000000000000
rdx0x00000000000000000
rsi0x00000000000000000
rdi0x00000000000000000
rbp0x00000000000000000
rsp0x00007fffffffdb10140737488345872
r80x00000000000000000
r90x00000000000000000
r100x00000000000000000
r110x00000000000000000
r120x00000000000000000
r130x00000000000000000
r140x00000000000000000
r150x00000000000000000
rip0x00000000004017404200256
eflags0x0000000000000200512

Memory Mappings

Start AddressEnd AddressSizePermissionsName
0x4000000x4010000x1000r--p/home/czc/jyy/address-space/simple
0x4010000xe7f0000xa7e000r-xp/home/czc/jyy/address-space/simple
0xe7f0000xea50000x26000r--p/home/czc/jyy/address-space/simple
0xea50000xeac0000x7000rw-p/home/czc/jyy/address-space/simple
0xeac0000x18b20000xa06000rw-p[heap]
0x7ffff7ffa0000x7ffff7ffe0000x4000r--p[vvar]
0x7ffff7ffe0000x7ffff7fff0000x1000r-xp[vdso]
0x7ffffffdd0000x7ffffffff0000x22000rw-p[stack]

readelf

sh
$ readelf -h simple
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - GNU
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x401740
  Start of program headers:          64 (bytes into file)
  Start of section headers:          11269904 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         10
  Size of section headers:           64 (bytes)
  Number of section headers:         34
  Section header string table index: 33

入口地址和上面的rip一致


  • 内存空间里,从0x400000开始之前的位置都是不能访问的地方
  • vvar是数据(不可执行)
  • vdso是代码(可执行)
  • 系统调用不一定进入内核
Pasted image 20250320190800

初始化栈,从栈顶(低地址)开始是 argc,argv,0,指针数组。

  • 进程的初始状态
    • 只有elf文件里声明的内存
  • 一定有系统调用可以改变进程的地址空间
  • Memory Map系统调用
c
// 映射
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 
int munmap(void *addr, size_t length); // 修改映射权限 
int mprotect(void *addr, size_t length, int prot);
//e.g.    
void *mem1 = mmap(
        NULL,                   // Let the kernel choose the address
        4096,                   // Allocate 4KB
        PROT_READ | PROT_WRITE, // Read and write permissions
        MAP_PRIVATE | MAP_ANONYMOUS, // Private mapping not backed by a file
        -1,                     // No file descriptor needed for anonymous mapping
        0                       // No offset
    );
void *exe_map = mmap(
        NULL,                   // Let the kernel choose the address
        st.st_size,             // Map the whole file
        PROT_READ,              // Read-only permissions
        MAP_PRIVATE,            // Private mapping
        fd,                     // File descriptor
        0                       // Start from the beginning of the file
    );
  • linux使用ps查看进程号
  • pmap +pid可以查看进程的地址空间
  • pmap <pid>可以打印一个进程的地址空间
    • 有一个文件夹是/proc/<pid> 对这个文件进行ls可以看到进程里面有什么信息maps文件就是进程的文件夹
    • strace pmap <pid> &| vim -a可以查看系统调用序列
  • 文件描述符打开文件int fd = open(argv[0], O_RDONLY);
  • gdb,p mem1查看指针地址
c
    // Example 2: Map the executable itself (argv[0])
    int fd = open(argv[0], O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }
    
    // Get file size
    struct stat st;
    if (fstat(fd, &st) == -1) {
        perror("fstat");
        close(fd);
        return 1;
    }
    
    // Map the executable file
    void *exe_map = mmap(
        NULL,                   // Let the kernel choose the address
        st.st_size,             // Map the whole file
        PROT_READ,              // Read-only permissions
        MAP_PRIVATE,            // Private mapping
        fd,                     // File descriptor
        0                       // Start from the beginning of the file
    );
  • 映射一个文件到内存中
  • alloc申请内存只需要放开权限而不需要真的申请。到真的访问的时候产生一个缺页异常,检查权限再分配
  • 磁盘前512个字节可以打印出来,磁盘512字节末尾可启动标记是0x55AA
python
import hexdump
import mmap
with open('/dev/sda', 'rb') as fp: mm = mmap.mmap(fp.fileno(), prot=mmap.PROT_READ, length=128 << 30) hexdump.hexdump(mm[:512])

入侵地址空间

  • GDB
  • 修改程序状态,修改地址空间

金手指:直接物理劫持内存

- 听起来很离谱,但 “卡带机” 时代的确可以做到!卡带上ROM(静态素材,代码素材)和RAM一起映射到内存地址空间
- 为什么叫“外挂”?因为真的是外挂。
![center](https://jyywiki.cn/OS/img/game-genie.webp)
- 今天我们有 Debug Registers 和 [Intel Processor Trace](https://perf.wiki.kernel.org/index.php/Perf_tools_support_for_Intel%C2%AE_Processor_Trace)
- 帮助系统工具 “合法入侵” 地址空间
	- 计算机给rom一个地址,rom返回一个值
		- 添加功能模拟行为,传输地址信号和数据信号
		- 增加查找表LUT,运行从这里执行配置。在ROM传递信号时,读到一处替换成另一个值
		- https://www.howtogeek.com/706248/what-was-the-game-genie-cheat-device-and-how-did-it-work/
- 现在的游戏游戏动态分配内存,地址不一样,解决方案:
	##### 查找 + Filter
		- 进入游戏时 exp=4950exp=4950
		- 打了个怪 exp=5100exp=5100
		- 符合 4950→5100 4950→5100 变化的内存地址是**很少**的
	    - 好了,出门就是满级了
    - ***金山游侠*** Hack DirectX游戏内呼叫
    - 类似游戏的调试器
    - `      string memfile = "/proc/" + to_string(pid) + "/mem";`查看进程的内存,把内存当文件打开
- DMA外挂
	- 内存ddr上加一个一样的卡拷贝内存
	- 用设备和操作系统共享内存,可以读取内存
- 采集视频信号
	- 采集卡 ([MS2130](https://jyywiki.cn/OS/manuals/MS2130.pdf)) + 树莓派 = 外挂
	- 输出视频信号,使用模式识别检测视频信号并标记。
	- FPGA,使用IP核合成视频
	- 可以作为copilot

上次更新于: